import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.Vector;

/**
 * Creates connections with all peers.
 * Accepts new connections from peers.
 * 
 * 
 * @author io_porges
 *
 */
public class PeersInterface {

	public static final int maxPeers = 50;
	public static final int maxRequests = 5;

	private TreeSet<PeerHandler> connectedPeers;
	private GlobalTorrentInfo info;
	private Thread peerMonitor;
	private int lastPeerTried;
	private DecimalFormat formatter;


	public String printMe(){
		System.out.println("print me");
		String s = connectedPeers.toString();
		System.out.println(s);
		return s;
	}

	public void printContacts () {
		synchronized (connectedPeers) {
			int count = 0;
			int unchoke = 0;
			Iterator<PeerHandler> itr = connectedPeers.iterator();
			while (itr.hasNext()) {
				PeerHandler ph = itr.next();
				if (ph.connected())
					count++;
				if (!ph.isChoked())
					unchoke++;
//				System.out.print(itr.next() + "\t\t");
			}
			if(GlobalTorrentInfo.debugProgress) {
				String prog = formatter.format(info.dataFileHandler.printProgress()) + '%';
				String ratio = formatter.format(info.tracker.ratio());
				System.out.println(prog +" ("+unchoke+"-"+count+"-"+connectedPeers.size()+") "+ratio);
			}
		}
	}

	public PeersInterface(GlobalTorrentInfo info) {
		connectedPeers = new TreeSet<PeerHandler> ();
		this.info = info;
		info.peers = this;
		lastPeerTried = -1;
		formatter = new DecimalFormat ();
		formatter.setMaximumFractionDigits(2);
		formatter.setMinimumFractionDigits(2);
	}

	public void startIncomingPeerConnection (Socket s, String peerID) {
		String IP = s.getInetAddress().getHostAddress();
		int port = s.getPort();
		PeerInfo p = new PeerInfo (peerID, IP, port);
		synchronized (connectedPeers) {
			if (connectedPeers.size() >= maxPeers+5) {
				System.out.println("Too many peers: "+p.address());
				return;
			}
			PeerHandler ph = PeerHandler.startIncomingPeer(s, p, info);
			connectedPeers.add(ph);
		}
	}

	public void startOutgoingPeerConnection (PeerInfo peer) {
		synchronized (connectedPeers) {
			if (connectedPeers.size() >= maxPeers) {
				System.out.println("Too many peers: "+peer.address());
				return;
			}
			PeerHandler ph = PeerHandler.startOutgoingPeer(peer, info);
			connectedPeers.add(ph);
		}
	}

	public boolean needPeer () {
		synchronized (connectedPeers) {
			return connectedPeers.size() < maxPeers;
		}
	}

	public void removePeerConnection (PeerHandler peer) {
		synchronized (connectedPeers) {
			connectedPeers.remove(peer);
//			if (needPeer())
//			startOutgoingPeerConnection(getNewPeer());
		}
		if (peer != null)
			peer.closeConnection();
	}

	public PeerInfo getNewPeer () {
		Vector<PeerInfo> trackerPeers = info.tracker.returnPeers();
		TreeSet<PeerInfo> connPeers;
		String myAddress;
		try {
			myAddress = InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			myAddress = "";
		}
		synchronized (connectedPeers) {
			connPeers = new TreeSet<PeerInfo>();
			Iterator<PeerHandler> itr = connectedPeers.iterator();
			while (itr.hasNext()) {
				connPeers.add(itr.next().peerInfo());
			}
		}
		if (lastPeerTried >= trackerPeers.size()-1)
			lastPeerTried = -1;
		for (int k=lastPeerTried+1; k<trackerPeers.size(); k++) {
			PeerInfo p = trackerPeers.get(k);
			if (!myAddress.equals(p.address()) && !connPeers.contains(p)) {
				lastPeerTried = k;
				return trackerPeers.get(k);
			}
		}
		for (int k=0; k<lastPeerTried; k++) {
			PeerInfo p = trackerPeers.get(k);
			if (!myAddress.equals(p.address()) && !connPeers.contains(p)) {
				lastPeerTried = k;
				return trackerPeers.get(k);
			}
		}
		return null;
	}

	public void killAllConnectedPeers () {
		Vector<PeerHandler> tmp;
		synchronized (connectedPeers) {
			tmp = new Vector<PeerHandler> (connectedPeers);
		}
		for (int k=0; k<tmp.size(); k++) {
			removePeerConnection(tmp.get(k));
		}
	}

	public void sendHaveMsg (int pieceIndex) {
		Vector<PeerHandler> p;
		synchronized (connectedPeers) {
			p = new Vector<PeerHandler> (connectedPeers);
			for (int k=0; k<p.size(); k++) {
				try {
					p.get(k).sendHave(pieceIndex);
				} catch (IOException e) {
					p.get(k).closeConnection();
				}
			}
		}
	}

	private void addPeersUntilFull () {
		synchronized (connectedPeers) {
			try {
				while (needPeer()) {
					PeerInfo p = getNewPeer();
					if (p == null)
						return;
					startOutgoingPeerConnection(p);
				}
			} catch (NullPointerException e) {}
		}
	}

	public void startPeerMonitor(){
		peerMonitor = new Thread (new PeerMonitor());
		continueRunning = true;
		peerMonitor.start();
	}
	private boolean continueRunning;
	private class PeerMonitor implements Runnable {
		public void run () {
			try {
				while (continueRunning) {
					printContacts();
					if (!info.isSeed()) {
						addPeersUntilFull();
						synchronized (connectedPeers) {
							Iterator<PeerHandler> itr = connectedPeers.iterator();
							while (itr.hasNext()) {
								PeerHandler ph = itr.next();
								try {
									ph.fillRequests();
								} catch (IOException e) {
									ph.closeConnection();
									break;
								}
							}
						}
					}
					Integer index = info.dataFileHandler.getNextHave();
					while (index != null) {
						sendHaveMsg(index);
						index = info.dataFileHandler.getNextHave();
					}
					Thread.sleep(1000);
				}
			} catch (InterruptedException e) {}
		}
	}

	public void stopPeers () {
		peerMonitor.interrupt();
		continueRunning = false;
	}
}
